/*
 * This file is part of aion-engine <aion-engine.com>
 *
 * aion-engine is private software: you can redistribute it and or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Private Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aion-engine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with aion-engine.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionengine.gameserver.taskmanager;

import com.aionemu.commons.database.dao.DAOManager;
import com.aionemu.commons.services.CronService;
import com.aionengine.gameserver.dao.ServerVariablesDAO;
import com.aionengine.gameserver.utils.ThreadPoolManager;
import org.quartz.CronExpression;

import java.text.ParseException;
import java.util.Date;

/**
 * @author Rolandas
 */
public abstract class AbstractCronTask implements Runnable {

    private String cronExpressionString;
    private CronExpression runExpression;
    private int runTime;
    private long period;

    /**
     * Timestamp in Seconds of the task run start, based on DB server variable
     */
    public final int getRunTime() {
        return runTime;
    }

    /**
     * The same as a milliseconds left, but any extended class may specify a
     * little delay. if delay is not needed then it's simple "runTime" minus "now"
     * function
     */
    abstract protected long getRunDelay();

    /**
     * Any pre-init, pre-load tasks when the instance is created
     */
    protected void preInit() {
    }

    /**
     * Any post-init, post-load tasks when the instance is created
     */
    protected void postInit() {
    }

    public final String getCronExpressionString() {
        return cronExpressionString;
    }

    /**
     * Variable name of the task start time stored in the server_variables DB
     * table
     */
    abstract protected String getServerTimeVariable();

    public long getPeriod() {
        return period;
    }

    /**
     * Tasks which have to be run before the actual scheduled task
     */
    protected void preRun() {
    }

    /**
     * The main execution code goes here
     */
    abstract protected void executeTask();

    /**
     * Is the task allowed to run on its initialization (if runDelay = 0) or only
     * at times defined by cron
     */
    abstract protected boolean canRunOnInit();

    /**
     * Tasks which have to be run after the task is complete and saved to DB
     */
    protected void postRun() {
    }

    public AbstractCronTask(String cronExpression) throws ParseException {
        if (cronExpression == null)
            throw new NullPointerException("cronExpressionString");

        cronExpressionString = cronExpression;

        ServerVariablesDAO dao = DAOManager.getDAO(ServerVariablesDAO.class);
        runTime = dao.load(getServerTimeVariable());

        preInit();
        runExpression = new CronExpression(cronExpressionString);
        Date nextDate = runExpression.getTimeAfter(new Date());
        Date nextAfterDate = runExpression.getTimeAfter(nextDate);
        period = nextAfterDate.getTime() - nextDate.getTime();
        postInit();

        if (getRunDelay() == 0) {
            if (canRunOnInit())
                ThreadPoolManager.getInstance().schedule(this, 0);
            else {
                saveNextRunTime();
            }
        }
        scheduleNextRun();
    }

    private void scheduleNextRun() {
        CronService.getInstance().schedule(this, cronExpressionString, true);
    }

    private void saveNextRunTime() {
        Date nextDate = runExpression.getTimeAfter(new Date());
        ServerVariablesDAO dao = DAOManager.getDAO(ServerVariablesDAO.class);
        runTime = (int) (nextDate.getTime() / 1000);
        dao.store(getServerTimeVariable(), runTime);
    }

    @Override
    public final void run() {
        if (getRunDelay() > 0) {
            ThreadPoolManager.getInstance().schedule(this, getRunDelay());
        } else {
            preRun();

            executeTask();
            saveNextRunTime();

            postRun();
        }
    }
}
